package org.esgl3d.loader;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ReadOnlyBufferException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.esgl3d.Mesh;
import org.esgl3d.Mesh.Format;
import org.esgl3d.core.ResourceResolver;
import org.esgl3d.math.Vector3d;
import org.esgl3d.rendering.Renderer;
import org.esgl3d.rendering.VertexContainer;
import org.esgl3d.rendering.VertexFormat;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.FaceDetector.Face;
import android.util.Log;

public class ObjLoader {
	
	private static class Float3 {
		public float x;
		public float y;
		public float z;
	}
	
	private static class Face3 {
		public int[] idx = new int[3];
		public int[] tex = new int[3];
		
		
		public void parseIndex(int i, String line) {
			StringTokenizer tokenizer = new StringTokenizer(line, "/");
			idx[i] = Integer.parseInt(tokenizer.nextToken());
			if (tokenizer.hasMoreTokens())
				tex[i]= Integer.parseInt(tokenizer.nextToken()); 
		}
	}
	
	private static class Float2 {
		public float u;
		public float v;
	}
	
	private static class Material {
		public String name = "";
		
		public ArrayList<Face3> faces = new ArrayList<Face3>();
	}
	
	private static final Logger logger = Logger.getLogger(ObjLoader.class.getName());
	private ResourceResolver resolver;
	private String resourceIdString;
	
	private final static String VERTEX = "v";
	private final static String FACE = "f";
	private final static String TEXCOORD = "vt";
	private final static String NORMAL = "vn";
	private final static String OBJECT = "o";
	private final static String MATERIAL_LIB = "mtllib";
	private final static String USE_MATERIAL = "usemtl";
	private final static String NEW_MATERIAL = "newmtl";
	private final static String DIFFUSE_TEX_MAP = "map_Kd";
	
	private ArrayList<Float3> vertices = new ArrayList<ObjLoader.Float3>();
	private ArrayList<Face3> faces = new ArrayList<ObjLoader.Face3>();
	private ArrayList<Float2> texturecoords = new ArrayList<ObjLoader.Float2>();
	private HashMap<String, Material> materials = new HashMap<String, Material>();
	
	public ObjLoader(ResourceResolver res, String resourceId) {
		resolver = res;
		resourceIdString = resourceId;
	}
	
	
	public Mesh createMesh(Renderer r, InputStream stream) {
	
		
		BufferedReader buffer = new BufferedReader(new InputStreamReader(resolver.getResourceStream(resourceIdString	)));
		
		String line;
		Material current = null;
		try {
			while ((line = buffer.readLine()) != null) {
				StringTokenizer parts = new StringTokenizer(line, " ");
				int numTokens = parts.countTokens();
				
				if (numTokens == 0)
					continue;
				
				String type = parts.nextToken();
				
				if (type.equals(VERTEX)) {
					if (logger.isLoggable(Level.FINEST))
						logger.finest("Vertex: "+line);
					Float3 f = new Float3();
					
					f.x = Float.parseFloat(parts.nextToken());
					f.y = Float.parseFloat(parts.nextToken());
					f.z = Float.parseFloat(parts.nextToken());
					vertices.add(f);
					
				} else if (type.equals(FACE)) {
					Face3 f = new Face3();
					f.parseIndex(0,parts.nextToken());
					f.parseIndex(1,parts.nextToken());
					f.parseIndex(2,parts.nextToken());
					faces.add(f);
					
					current.faces.add(f);
				} else if (type.equals(TEXCOORD)) {
					Float2 f = new Float2();
					f.u = Float.parseFloat(parts.nextToken());
					f.v = Float.parseFloat(parts.nextToken());
					texturecoords.add(f);
				} else if (type.equals(MATERIAL_LIB)) {
					parseMaterial(resolver.getResourceStream(parts.nextToken()));
				} else if (type.equals(USE_MATERIAL)) {
					current = materials.get(parts.nextToken());
				}
				
				
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		Mesh result = new Mesh();
		
		VertexContainer container = null;
		if (texturecoords.size() > 0)
			container = r.createVertexContainer(VertexFormat.F_2Texture_3Position, faces.size()*3);
		else
			container = r.createVertexContainer(VertexFormat.F_4Color_3Position, faces.size()*3);
		
		int num = 0;
		for (Material mat : materials.values()) {
		
			if (mat.name.length() > 0) {
				Bitmap loadedBitmap = BitmapFactory.decodeStream(resolver.getResourceStream(mat.name));
				result.setTexture(r.getTextureManager().addImage(loadedBitmap, false	));
			}
			
			for (Face3 curFace : mat.faces) {
				if (logger.isLoggable(Level.FINEST))
					logger.finest(String.format("Face: %d %d %d ", curFace.idx[0], curFace.idx[1], curFace.idx[2]));
				if (texturecoords.size() > 0) {
					foo(container, num, vertices.get(curFace.idx[0]-1), texturecoords.get(curFace.tex[0]-1));
					foo(container, num+1, vertices.get(curFace.idx[1]-1),texturecoords.get(curFace.tex[1]-1));
					foo(container, num+2, vertices.get(curFace.idx[2]-1),texturecoords.get(curFace.tex[2]-1));
				} else {
					foo(container, num, vertices.get(curFace.idx[0]-1), null);
					foo(container, num+1, vertices.get(curFace.idx[1]-1),null);
					foo(container, num+2, vertices.get(curFace.idx[2]-1),null);
				}
				num += 3;
			}
		}
		
		container.synchronize();
		
		
		
		
	
		result.setVertices(container, Format.Triangle);
		return result;
	}
	
	private void foo(VertexContainer c, int idx, Float3 bar, Float2 baz) {
		c.setColor(idx, 1,1,1,0.5f);
		c.setPosition(idx,bar.x/1,bar.y/1,bar.z/1 );
		if (baz != null)
			c.setTexture(idx, baz.u, 1-baz.v);
		
	}
	
	private void parseMaterial(InputStream stream) {
		BufferedReader buffer = new BufferedReader(new InputStreamReader(stream));
		
		Material current = null;
		
		String line;
		try {
			while ((line = buffer.readLine()) != null) {
				StringTokenizer parts = new StringTokenizer(line, " ");
				int numTokens = parts.countTokens();
				
				if (numTokens == 0)
					continue;
				
				String type = parts.nextToken();
				
				if (type.equals(NEW_MATERIAL)) {
					current = new Material();
					materials.put(parts.nextToken(), current);
				}
				
				if (type.equals(DIFFUSE_TEX_MAP)) {
					current.name = parts.nextToken();
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		
	}
}
